package org.erikaredmark.monkeyshines.tiles;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import org.erikaredmark.monkeyshines.GameConstants;
import org.erikaredmark.monkeyshines.resource.WorldResource;
/**
*
* How the tile reacts to the game world. These tile types are stateless; their nature represents all that needs to be known
* about a tile at a given point. Stateless tile types only ever differ in their id and underlying type. However, each instance
* IS unique. This is deliberate: Many collision detectiong algorithms can be simplified by not sharing instances (to determine if
* two points map to the same tile)
*
* <strong> Stateless</strong>
* <ul>
* <li> Solid: Can not be passed. Bonzo may stand on it, but can not otherwise go through it. There is one exception:
* if bonzo starts a screen inside of a solid, he may move out of it (and thus through it) but not back into it
* again.</li>
* <li> Thru: Bonzo may stand on it, but if he is walking through it on the side it will not impede his movement.
* He may not, however, go down through it (it is standable only)</li>
* <li> Scene: Has no effect on Bonzo. Has no effect on anything. Merely a stand-in for graphics. </li>
* <li> None: No tile. Acts as a null-safe way of simply saying "no tile" </li>
* </ul>
*
* @author Erika Redmark
*
*/
public class CommonTile implements TileType {
private final int id;
private final StatelessTileType underlyingType;
// Private state variables for drawing. Only recomputed when paint is called with a new resource,
// which is very rare.
private int tileDrawRow;
private int tileDrawCol;
private WorldResource lastPaintedRsrc;
public static final CommonTile NONE = new CommonTile(0, StatelessTileType.NONE);
private CommonTile(int id, StatelessTileType type) {
this.id = id;
this.underlyingType = type;
}
/**
*
* Static factory to return instances of common tiles. If it is decided to share instances
* and, going through this point will make it easier.
* <p/>
* Creates a new common tile instance. Common tiles have no state and draw most
* behaviour from a set of common enumerated types. This class is merely the container
* for the unique id and to give a unique constructed object per tile on a level.
*
* @param id
*
* @param type
*
* @param rsrc
* the initial expected world resource this tile will be drawn with. This causes
* drawing data to be computed before the paint method. This prevents slowdown
* for tiles never drawn yet when changing screens in the game
*
* @return
* new instance of this object
*
*/
public static CommonTile of(int id, StatelessTileType type, WorldResource rsrc) {
CommonTile tile = new CommonTile(id, type);
tile.recomputeDrawState(rsrc);
return tile;
}
/**
*
* Static factory to return instances of common tiles. If it is decided to share instances
* and, going through this point will make it easier.
* <p/>
* Creates a new common tile instance. Common tiles have no state and draw most
* behaviour from a set of common enumerated types. This class is merely the container
* for the unique id and to give a unique constructed object per tile on a level.
*
* @param id
*
* @param type
*
* @return
* new instance of this object
*
*/
public static CommonTile of(int id, StatelessTileType type) {
return new CommonTile(id, type);
}
public StatelessTileType getUnderlyingType() { return underlyingType; }
@Override public int getId() { return id; }
@Override public boolean isThru() { return underlyingType == StatelessTileType.THRU; }
@Override public boolean isSolid() { return underlyingType == StatelessTileType.SOLID; }
@Override public boolean isLandable() { return isSolid() || isThru(); }
@Override public void update() { /* No state; never updates */ }
@Override public void reset(boolean oddElseEven) { /* No op */ }
@Override public CommonTile copy() {
return new CommonTile(id, underlyingType);
}
@Override public void paint(Graphics2D g2d, int drawToX, int drawToY, WorldResource rsrc) {
if (this.underlyingType == StatelessTileType.NONE) return;
if (rsrc != lastPaintedRsrc) recomputeDrawState(rsrc);
g2d.drawImage(rsrc.getStatelessTileTypeSheet(this.underlyingType),
drawToX, drawToY, // Dest 1
drawToX + GameConstants.TILE_SIZE_X, drawToY + GameConstants.TILE_SIZE_Y, // Dest 2
tileDrawCol, tileDrawRow, // Src 1
tileDrawCol + GameConstants.TILE_SIZE_X, tileDrawRow + GameConstants.TILE_SIZE_Y, // Src 2
null);
}
/**
*
* Recomputes the location in the sprite sheet that this particular tile should draw from. This depends
* on two factors: the id of the tile, and the dimensions of the sprite sheet. To prevent slowdown, this
* is only recomputed when the world is painted with a different resource.
* <p/>
* It is an error to call this with a NONE tile.
*
* @param rsrc
* the new resource to recompute values for
*
*/
private void recomputeDrawState(WorldResource rsrc) {
BufferedImage tileSpriteSheet = rsrc.getStatelessTileTypeSheet(this.underlyingType);
int sheetCols = tileSpriteSheet.getWidth() / GameConstants.TILE_SIZE_X;
// Assume Integer division.
// No need to store id after we computed the bounds for the graphics.
tileDrawCol = (id % sheetCols) * GameConstants.TILE_SIZE_X;
tileDrawRow = (id / sheetCols) * GameConstants.TILE_SIZE_Y;
// Sanity check: If the tileId goes out of bounds of the tile sheet, there is an issue. Print out that there
// is a rouge invisible tile.
// TODO Note: Document somehow that this DOESN'T prevent invisible tiles from accidentally being inserted by the
// editor. If the sheet has a fully transparent tile within the rectangle, that is technically valid. Perhaps
// have tilesheets fully pink everywhere else to communicate a bad-tile so this check always works?
int sheetRows = tileSpriteSheet.getHeight() / GameConstants.TILE_SIZE_Y;
if (id > sheetCols * sheetRows) {
System.err.println("" + this + ": Out of graphics range (Given sprite sheet only permits ids up to " + sheetCols * sheetRows);
}
}
public enum StatelessTileType {
SOLID,
THRU,
SCENE,
NONE;
}
@Override public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof CommonTile) ) return false;
CommonTile other = (CommonTile) o;
return this.id == other.id
&& this.underlyingType.equals(other.underlyingType);
}
@Override public int hashCode() {
int result = 17;
result += result * 31 + underlyingType.hashCode();
result += result * 31 + id;
return result;
}
@Override public String toString() {
return this.equals(CommonTile.NONE)
? "No Tile"
: "Common Tile of id " + id + " with underlying type " + underlyingType;
}
}